#include "mini_crt.h"

#define A_DOWN_CASE (0)
#define A_UP_CASE   (1)
#define A_IGNORE    (-1)
#define A_CHAR_D    ('d')

static void mem_dbg_check();

static int _debug_level = 0;
#define MINI_DEBUG_DEF  0
#define MINI_DEBUG_MEM  1
#define DEBUG_MEM_PRE(pre, fmt, ...) if(MINI_DEBUG_MEM & _debug_level) { \
    printf(pre "%s:%d fn:%s " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
}
#define DEBUG_MEM(fmt, ...) DEBUG_MEM_PRE("", fmt, __VA_ARGS__)


typedef struct _heap_hdr {
	enum {
		HEAP_BLOCK_FREE = 0xFDEEFDEE,
		HEAP_BLOCK_USED = 0xED0000ED,
	} type;
	int size; // include HEAP_HDR 's size
	struct _heap_hdr* next;
	struct _heap_hdr* pre;
} HEAP_HDR;
#define VA(addr, v) ((char*)addr + v)
#define HEAP_HDR_SIZE (sizeof(HEAP_HDR))
#define HEAP_SIZE (64 * 1024 * 1024) //64M
static HEAP_HDR* hp_hdr = NULL;

int mini_crt_heap_init() {
	void* base = brk(0);
	void* end = VA(base, HEAP_SIZE);
	if(!brk(end)) {//set heap
		return -1;
	} else {
		hp_hdr = (HEAP_HDR*)base;
		hp_hdr->size = HEAP_SIZE;
		hp_hdr->type = HEAP_BLOCK_FREE;
		hp_hdr->next = NULL;
		hp_hdr->pre = NULL;
	}
	return 0;
}

int mini_crt_io_init() {
	return 0;
}

//---------------------------malloc----------------------------------
void* malloc(const int size) {
    HEAP_HDR* it = hp_hdr;
    void* ret = 0;
    int inter_size = size + HEAP_HDR_SIZE;

    DEBUG_MEM_PRE("\n", "required malloc-size=%d", size);
    if(0 == size) {
        return 0;
    }

    while(it) {
        if (HEAP_BLOCK_USED == it->type) {
            it = it->next;
            continue;
        }

        if(it->size >= inter_size) {//find it
            DEBUG_MEM("find block@0x%x @size=0x%x", it, it->size);
            it->type = HEAP_BLOCK_USED;
            if((it->size - inter_size) > HEAP_HDR_SIZE) {//need split
                HEAP_HDR* new_block = (HEAP_HDR*)VA(it, inter_size);
                new_block->size = it->size - inter_size;
                new_block->type = HEAP_BLOCK_FREE;

                if(it->next) {
                    it->next->pre = new_block;
                }

                new_block->next = it->next;
                it->next = new_block;
                new_block->pre = it;
                it->size = inter_size;
            }

            DEBUG_MEM("return malloc_addr:0x%x", VA(it, HEAP_HDR_SIZE));
            mem_dbg_check();
            return VA(it, HEAP_HDR_SIZE);
        }
        it = it->next;
    }
    perror("[ERR]malloc!\n");
    return ret;
}

void free(void* ptr) {
    HEAP_HDR* it = hp_hdr;

    DEBUG_MEM("free@addr:0x%x hdr_addr@0x%x", ptr, VA(ptr, -HEAP_HDR_SIZE));
    while(it) {
        if(VA(it, HEAP_HDR_SIZE) == ptr) {
            it->type = HEAP_BLOCK_FREE;
            if(it->next) {
                if(HEAP_BLOCK_FREE == it->next->type) {//merge with next
                    it->size += it->next->size;
                    if(it->next->next) {
                        it->next->next->pre = it;
                    }
                    it->next = it->next->next;
                }
            }
            if(it->pre) {
                if(HEAP_BLOCK_FREE == it->pre->type) {//merge with pre
                    it->pre->size += it->size;
                    if(it->next) {
                        it->next->pre = it->pre;
                    }
                    it->pre->next = it->next;
                }
            }
            mem_dbg_check();
            return;
        }
        it = it->next;
    }
    perror("[ERR]free!\n");
}

void* memset(void* s, int c, size_t n) {
    char* p = s;
    while(n) {
        p[n - 1] = c;
        --n;
    }
    return s;
}

int memcmp(const void* s1, const void* s2, size_t n) {
    char* p1 = (char*)s1;
    char* p2 = (char*)s2;

    while(n) {
        if(*p1 != *p2) {
            return (*p1 - *p2);
        }
        --n;
        ++p1;
        ++p2;
    }
    return 0;
}

//---------------------------io----------------------------------
FILE* fopen(const char* path, const char* mode) {
    FILE* fd = (FILE*)-1;
    int flags = 0;
    int access = 00700;
#define O_RDONLY         00
#define O_WRONLY         01
#define O_RDWR           02
#define O_CREAT        0100 /* not fcntl */
#define O_TRUNC       01000 /* if file exist, len cut to 0*/
    if(0 == strcmp("w", mode)) {
        flags |= (O_WRONLY | O_CREAT | O_TRUNC);
    } else if(0 == strcmp("w+", mode)) {
        flags |= (O_RDWR | O_CREAT | O_TRUNC);
    } else if(0 == strcmp("r", mode)) {
        flags |= (O_RDONLY);
    } else if(0 == strcmp("r+", mode)) {
        flags |= (O_RDONLY | O_CREAT);
    }
    fd = (FILE*)open(path, flags, access);
    return fd;
}

size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) {
    if(size) {
        return read(stream, ptr, size * nmemb) / size;
    } else {
        return 0;
    }
}

size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) {
    if(size) {
        return write(stream, ptr, size * nmemb) / size;
    } else {
        return 0;
    }
}

int fclose(FILE* fp) {
    return close(fp);
}

int fseek(FILE* stream, long offset, int whence) {
    return 0;//seek((int)stream, offset, whence);
}

int fputc(int c, FILE* stream) {
    if(1 != write(stream, &c, 1)) {
        return EOF;
    } else {
        return c;
    }
}

int fputs(const char* s, FILE* stream) {
    int len = strlen(s);
    if(len != write(stream, s, len)) {
        return EOF;//ng:-1
    } else {
        return 1;//ok:non-neg
    }
}

int printf(const char* format, ...) {
    va_list va;
    va_start(va, format);
    return vprintf(format, va);
}

int vprintf(const char* format, va_list va) {
    const char* itf = format;
    int st = 0;
    char itoa_buf[32] = {0};
    while(*itf) {
        switch(st) {
        case 0://idle
            if('%' == *itf) {
                st = 1;
            } else {
                fputc(*itf, stdout);
            }
            break;
        case 1:
            if ('x' == *itf) {//catch %x
                int i = va_arg(va, long);
                itoa(i, 16, A_DOWN_CASE, itoa_buf);
                fputs(itoa_buf, stdout);
                st = 0;
            } else if('X' == *itf) {//catch %X
                int i = va_arg(va, long);
                itoa(i, 16, A_UP_CASE, itoa_buf);
                fputs(itoa_buf, stdout);
                st = 0;
            } else if('s' == *itf) {//catch %s
                char* s = va_arg(va, char*);
                if(s) {
                    fputs(s, stdout);
                } else {
                    fputs("(null)", stdout);
                }
                st = 0;
            } else if('d' == *itf) {//catch %d
                int i = va_arg(va, long);
                itoa(i, 10, A_CHAR_D, itoa_buf);
                fputs(itoa_buf, stdout);
                st = 0;
            } else if('u' == *itf) {//catch %u
                int i = va_arg(va, unsigned long);
                itoa(i, 10, A_IGNORE, itoa_buf);
                fputs(itoa_buf, stdout);
                st = 0;
            } else if('%' == *itf) {
                fputc('%', stdout);
                st = 0;
            } else {
                fputc('%', stdout);
                fputc(*itf, stdout);
                fputs("(not suport)", stdout);
                st = 0;
            }
            break;
        default:
            break;
        }
        ++itf;
    }
    return 0;
}

void perror(const char* s) {
    fwrite(s, strlen(s), 1, stderr);
}

//---------------------------string----------------------------------
int strcmp(const char* s1, const char* s2) {
    if(!s1 || !s2) {
        return -1;
    }

    while(*s1 != '\0' && *s1 == *s2) {
        ++s1;
        ++s2;
    }
    if(0 == *s1 && 0 == *s2) {
        return 0;
    } else {
        return (s1 - s2);
    }
}

size_t strlen(const char* s) {
    const char* it = s;
    while(*it) {
        ++it;
    }
    return (it - s);
}

char* strcpy(char* dest, const char* src) {
    int i = 0;
    for (; src[i] != '\0' ; i++) {
        dest[i] = src[i];
    }
    dest[i] = '\0';
    return dest;
}

int atoi (const char* ptr) {//todo
    return 0;
}

int itoa(int i, int base, int aux, char* __out__ buf) {
    char i2a_map[16] = "0123456789abcdef";
    char i2a_map_up[16] = "0123456789ABCDEF";
    int r = 0; //the most right pos's value
    int j = 0; //itorator
    unsigned u = 0;
    unsigned _u = 0;

    u = (unsigned)i;
    if((u & 0x80000000) && ('d' == aux)) {//neg-pos
        u = (~u + 1);//get the 2's complement
        ++j;
        buf[0] = '-';
    }

    //calc out_buf_len
    _u = u;
    do {
        ++j;
        _u /= base;
    } while(_u);

    _u = u;
    r = _u % base;
    buf[j] = '\0';
    do {
        if(A_UP_CASE == aux) {
            buf[j - 1] = i2a_map_up[r];
        } else {
            buf[j - 1] = i2a_map[r];
        }
        --j;
        _u /= base;
        r = _u % base;
    } while(_u);
    return 0;
}

//---------------------------debug----------------------------------
static void mem_dbg_check() {
    HEAP_HDR* it = hp_hdr;

    if(!(_debug_level & MINI_DEBUG_MEM)) {
        return;
    }

    printf("[start    -      end] type size\n");
    while(it) {
        printf("[0x%x-0x%x] ", it, VA(it, it->size - 1));
        if(HEAP_BLOCK_FREE == it->type) {
            printf("FREE ");
        } else if(HEAP_BLOCK_USED == it->type) {
            printf("USED ");
        } else {
            printf("ERR! ");
        }
        printf("0x%x(%d)\n", it->size, it->size);
        it = it->next;
    }
    printf("\n");
}

char* get_env(char** argv, const char* key) {
    char** env = argv;
    int i = 0;

    while(*env) {
        ++env;
    }
    ++env;

    int len_key = strlen(key);
    while(env[i]) {
        if(0 == memcmp(key, env[i], len_key)) {
            return &env[i][len_key + 1];
        }
        ++i;
    }
    return NULL;
}

int debug_init(char** argv) {
    if(0 == strcmp("mem", get_env(argv, "MINI_DEBUG"))) {
        _debug_level |= MINI_DEBUG_MEM;
    } else {
        _debug_level = MINI_DEBUG_DEF;
    }
    return 0;
}
